package com.firexxx.study.visitor;

import com.firexxx.study.TimeCache;
import com.firexxx.study.annotation.TimeCost;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.util.HashSet;
import java.util.Set;

/**
 * @description: TODO
 * @throws:
 * @author: zhuangzhang2
 * @date: 2020/07/05 10:21
 */
public class TimeMethodVisitor1 extends MethodVisitor {
    private String timeCacheClassName = TimeCache.class.getName().replace(".", "/");
    private boolean timeCostExist;
    private String owner;
    private String name;
    private String curMethodName = null;
    private static Set<String> skipOwnerSet = new HashSet<>();
    private static Set<String> skipNameSet = new HashSet<>();

    static {
        skipOwnerSet.add("java/lang/");
        skipOwnerSet.add("java/util/");
        skipOwnerSet.add("sun/");
        skipNameSet.add("<init>");
    }

    public TimeMethodVisitor1(MethodVisitor mv, String owner, String name) {
        super(Opcodes.ASM7, mv);
        this.owner = owner;
        this.name = name;
    }

    public boolean isTimeCostExist() {
        return timeCostExist;
    }

    public void setTimeCostExist(boolean timeCostExist) {
        this.timeCostExist = timeCostExist;
    }

    private void setStartTime(String name) {
        mv.visitLdcInsn(name);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC,
                "java/lang/System",
                "currentTimeMillis",
                "()J",
                false);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC,
                timeCacheClassName,
                "setStartTime",
                "(Ljava/lang/String;J)V",
                false);
    }

    private void setEndTime(String name) {
        mv.visitLdcInsn(name);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC,
                "java/lang/System",
                "currentTimeMillis",
                "()J",
                false);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC,
                timeCacheClassName,
                "setEndTime",
                "(Ljava/lang/String;J)V",
                false);
    }

    private void getCostTime(String name, int level) {
        mv.visitFieldInsn(Opcodes.GETSTATIC,
                "java/lang/System",
                "out",
                "Ljava/io/PrintStream;");
        if (level == 1) {
            mv.visitLdcInsn("-");
        } else {
            mv.visitLdcInsn("--");
        }
        mv.visitLdcInsn(name);

        mv.visitMethodInsn(Opcodes.INVOKESTATIC,
                timeCacheClassName,
                "getCostTime",
                "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
                false);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
                "java/io/PrintStream",
                "println",
                "(Ljava/lang/String;)V",
                false);
    }

    private boolean skip(String owner, String name) {
        for (String skipOwner : skipOwnerSet) {
            if (owner.startsWith(skipOwner)) {
                return true;
            }
        }

        for (String skipName : skipNameSet) {
            if (name.equals(skipName)) {
                return true;
            }
        }

        return false;
    }

//    @Override
//    public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
//        if (timeCostExist) {
//
//
//            super.visitTryCatchBlock(start, end, handler, type);
//        } else {
//            super.visitTryCatchBlock(start, end, handler, type);
//        }
//    }

    @Override
    public void visitCode() {
        if (timeCostExist && !skip(owner, name)) {
            //方法体内开始时调用
            setStartTime(name);

            super.visitCode();
        } else {
            super.visitCode();
        }
    }

    @Override
    public void visitInsn(int opcode) {
        if (timeCostExist) {
            if (opcode <= Opcodes.RETURN && opcode >= Opcodes.IRETURN) {
                //输出最后一个方法的运行时间
                if (curMethodName != null && !skip(owner, curMethodName)) {
                    setEndTime(curMethodName);
                    getCostTime(curMethodName, 2);
                }

                //输出整体方法的时间
                if (!skip(owner, name)) {
                    setEndTime(name);
                    getCostTime(name, 1);
                }
            }

            super.visitInsn(opcode);
        } else {
            super.visitInsn(opcode);
        }
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
        if (timeCostExist) {
            // SetEndTime , 第一次过滤掉，因为是没有方法体的
            if(curMethodName != null && !skip(owner, curMethodName)) {
                setEndTime(curMethodName);
                getCostTime(curMethodName, 2);
            }

            if (!skip(owner, name)) {
                // 重新设置当前方法
//            curMethodName = owner.replace("/",".") + "@" + name;
                curMethodName = name;

                // 当前方法set进去
                setStartTime(curMethodName);
            }

            super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
        } else {
            super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
        }
    }

    @Override
    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
        if (descriptor != null && descriptor.replace("/", ".").contains(TimeCost.class.getName())) {
            timeCostExist = true;
        }

        return super.visitAnnotation(descriptor, visible);
    }
}